use async_trait::async_trait;

use my_rust_core::{
    security::jwt::Role,
    shared::{self, app_config_service},
};

use std::{path::Path, sync::Arc};

use sea_orm::{
    ColumnTrait, Condition, DatabaseConnection, EntityTrait, FromQueryResult, JoinType,
    QueryFilter, QuerySelect, RelationTrait,
};

use crate::entities;

use super::{
    account_group_user_service::AccountGroupUserService, user_role_service::UserRoleService,
};

/// This is used by SeaORM into_model function.
#[allow(unused)]
#[derive(FromQueryResult, Debug)]
pub struct QueryStatusModel {
    pub id: String,
    pub username: String,
    pub enabled: bool,
    pub account_expired: bool,
    pub account_locked: bool,
    pub password_expired: bool,
}

#[allow(unused)]
#[derive(Debug)]
pub struct QueryDetailsModel {
    pub account_group_id: String,
    pub id: String,
    pub username: String,
    pub full_name: String,
    pub description: Option<String>,
    pub avatar_url: String,
    pub is_account_group_creator: bool,
    pub enabled: bool,
    pub full_roles: Vec<Role>,
}

#[async_trait]
pub trait UserService: Send + Sync {
    /// Validates if the password matches the user stored password.
    /// Return: True means password matches. False means not.
    async fn validate_password(&self, username: &str, password: &str) -> bool;

    /// Queries user status data. Return user only contains ID, Username, Enabled, AccountExpired, AccountLocked, PasswordExpired fields.
    async fn query_status(&self, username: &str) -> Option<QueryStatusModel>;

    /// Gets user avatar url based on its filename.
    /// Return: The full url of user avatar.
    fn get_avatar_url(&self, avatar_filename: Option<&str>) -> String;

    /// Queries the user details by id or username.
    async fn query_details(
        &self,
        account_group_id_param: Option<String>,
        id_or_username: &str,
    ) -> Option<QueryDetailsModel>;
}

pub struct UserServiceImpl {
    db: Arc<DatabaseConnection>,
    account_group_user_service: Arc<dyn AccountGroupUserService>,
    user_role_service: Arc<dyn UserRoleService>,
}

impl UserServiceImpl {
    pub fn new(
        db: Arc<DatabaseConnection>,
        account_group_user_service: Arc<dyn AccountGroupUserService>,
        user_role_service: Arc<dyn UserRoleService>,
    ) -> Self {
        UserServiceImpl {
            db,
            account_group_user_service,
            user_role_service,
        }
    }
}

#[async_trait]
impl UserService for UserServiceImpl {
    /// Validates if the password matches the user stored password.
    /// Return: True means password matches. False means not.
    async fn validate_password(&self, username: &str, password: &str) -> bool {
        let mut is_valid = false;

        let password_hash: Option<String> = entities::prelude::User::find()
            .select_only()
            .column(entities::user::Column::Password)
            .filter(entities::user::Column::Username.eq(username))
            .into_tuple()
            .one(self.db.as_ref())
            .await
            .unwrap();

        if let Some(password_hash) = password_hash {
            // This action is highly time consuming. Because Argon2 is a memory- and CPU-intensive hashing algorithm designed specifically to slow down brute-force attacks.
            is_valid = shared::argon2_encryption::verify_password(password, &password_hash);
        }

        is_valid
    }

    /// Queries user status data. Return user only contains ID, Username, Enabled, AccountExpired, AccountLocked, PasswordExpired fields.
    async fn query_status(&self, username: &str) -> Option<QueryStatusModel> {
        // Queries the user status by username.
        // Please notice, theses are database column names in Select func. NOT struct field names.
        let user = entities::prelude::User::find()
            .select_only()
            .columns([
                entities::user::Column::Id,
                entities::user::Column::Username,
                entities::user::Column::Enabled,
                entities::user::Column::AccountExpired,
                entities::user::Column::AccountLocked,
                entities::user::Column::PasswordExpired,
            ])
            .filter(entities::user::Column::Username.eq(username))
            .into_model::<QueryStatusModel>()
            .one(self.db.as_ref())
            .await
            .unwrap();

        user
    }

    /// Gets user avatar url based on its filename.
    /// Return: The full url of user avatar.
    fn get_avatar_url(&self, avatar_filename: Option<&str>) -> String {
        let config = &app_config_service::APP_CONFIG;

        if let Some(avatar_filename) = avatar_filename {
            let avatar_file_full_name = Path::new(&config.resource.upload.root_file_path)
                .join(Path::new(&config.resource.avatar_directory_name))
                .join(Path::new(avatar_filename));

            if !avatar_file_full_name.exists() {
                return config.resource.default_avatar_url.clone();
            }

            return format!(
                "{}/{}/{}",
                config.resource.http_base_url,
                config.resource.avatar_directory_name,
                avatar_filename
            );
        }

        config.resource.default_avatar_url.clone()
    }

    /// Queries the user details by id or username.
    async fn query_details(
        &self,
        account_group_id_param: Option<String>,
        id_or_username: &str,
    ) -> Option<QueryDetailsModel> {
        let mut query = entities::prelude::User::find().select_only().columns([
            entities::user::Column::AccountGroupId,
            entities::user::Column::Id,
            entities::user::Column::Username,
            entities::user::Column::FullName,
            entities::user::Column::AvatarFilename,
            entities::user::Column::Description,
            entities::user::Column::Enabled,
        ]);

        if let Some(account_group_id) = account_group_id_param.clone() {
            query = query
                .join(
                    JoinType::InnerJoin,
                    entities::user::Relation::AccountGroupUser.def(),
                )
                .filter(
                    Condition::all()
                        .add(
                            Condition::any()
                                .add(entities::user::Column::Id.eq(id_or_username))
                                .add(entities::user::Column::Username.eq(id_or_username)),
                        )
                        .add(
                            entities::account_group_user::Column::AccountGroupId
                                .eq(account_group_id),
                        ),
                );
        } else {
            query = query.filter(
                Condition::any()
                    .add(entities::user::Column::Id.eq(id_or_username))
                    .add(entities::user::Column::Username.eq(id_or_username)),
            );
        }

        let result: Option<(
            String,
            String,
            String,
            String,
            Option<String>,
            Option<String>,
            bool,
        )> = query.into_tuple().one(self.db.as_ref()).await.unwrap();

        if let Some((
            account_group_id,
            id,
            username,
            full_name,
            avatar_filename,
            description,
            enabled,
        )) = result
        {
            let creator_account_group_id = match account_group_id_param {
                Some(id) => id,
                None => account_group_id.clone(),
            };

            let is_account_group_creator = self
                .account_group_user_service
                .count_by_account_group_and_user_and_is_account_group_creator(
                    &creator_account_group_id,
                    &id,
                )
                .await
                .unwrap()
                > 0;

            let full_roles = self
                .user_role_service
                .query_full_roles(&account_group_id, &id)
                .await;

            let result = QueryDetailsModel {
                account_group_id,
                id,
                username,
                full_name,
                description,
                avatar_url: self.get_avatar_url(avatar_filename.as_deref()),
                is_account_group_creator,
                enabled,
                full_roles,
            };

            return Some(result);
        }

        None
    }
}
